Skip to content

feat(MerkleTree): add tweaked Merkle authentication paths with completeness and tweak-tagged collision binding - #474

Merged
quangvdao merged 9 commits into
Verified-zkEVM:mainfrom
Abraxas1010:feat/merkle-tweaked-paths
Aug 10, 2026
Merged

feat(MerkleTree): add tweaked Merkle authentication paths with completeness and tweak-tagged collision binding#474
quangvdao merged 9 commits into
Verified-zkEVM:mainfrom
Abraxas1010:feat/merkle-tweaked-paths

Conversation

@Abraxas1010

@Abraxas1010 Abraxas1010 commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Body rewritten 28 July to match the code as it now stands. The original text described the pre-rescope Tweaked naming and the SM-TCR wiring that was removed during review; the summary bot correctly flagged the discrepancy. History is in the comments.

Summary

A node-addressed Merkle tree engine: nodeHash : NodeAddress s → α → α → α, where NodeAddress s is the typed root-path position of an internal node. Building, putative-root recomputation, completeness and constructive collision tracing are defined and proven once for an arbitrary nodeHash; the ordinary tree (constant), the level-separated tree (through NodeAddress.subtreeDepth) and XMSS/SLH-DSA-style fully-addressed trees (through an arbitrary address-to-tweak map) are its instances.

Per-level / per-node domain separation is the tree layout of the XMSS family — SLH-DSA / SPHINCS+ H, and the lean-Ethereum leanSig proposal — and is what lets security rest on target-collision resistance rather than full collision resistance. This is phase 1 of the lane now tracked as #505.

Main results (#print axioms in parentheses)

Addressed/Basic.lean — the engine:

  • populateUpAddressed / buildMerkleTreeAddressedWithHash / getPutativeRootAddressedWithHash. The unaddressed generateProof is reused unchanged; proofs carry no addresses. The address is threaded by precomposing nodeHash with .inL / .inR, so there is no path accumulator.
  • addressed_functional_completeness (no axioms at all) — honest paths verify, for every nodeHash.
  • AddressedCollision, findCollisionAddressed, findCollisionAddressed_sound, findCollisionAddressed_isSome, getPutativeRootAddressedWithHash_binding_collision ([propext]) — the constructive binding kernel: two distinct leaf values verifying to the same root at the same index yield a collision as data, tagged with the address at which it occurs.
  • findCollisionAddressed_oriented / addressed_oriented_binding ([propext]) — against an honest first opening, the first endpoint of the returned collision is exactly the child pair stored in the cache at the returned address, i.e. a value fixed at build time before any adversarial opening exists. This is the directional content a target-collision reduction needs, exposed as data.

Addressed/Level.lean — the level-separated instance (levelNodeHash), with level_functional_completeness and level_oriented_binding obtained purely by specialization; nothing is reproved.

Subsumption certificates

Added in response to review, to make the "one engine" claim a theorem rather than prose (all [propext]):

  • buildMerkleTreeAddressed_const, populateUpAddressed_const, getPutativeRootAddressed_const — the constant instance recovers the unaddressed computations.
  • functional_completeness_of_addressed — the unaddressed completeness theorem is derived from the engine's, not reproved.
  • findCollisionAddressed_const — erasing the address tag from the engine's collision walk gives InductiveMerkleTree.findCollision on the nose. The two collision kernels are one function up to the address decoration.
  • levelNodeHash_eq_addressedrfl; the level instance is definitionally the fully-addressed one at tweakOf = tweakAt ∘ subtreeDepth.

Scope, stated explicitly

This is a staged parallel engine, not a replacement. The pre-existing MerkleTree.Inductive definitions stand unchanged alongside it; the subsumption above is propositional, not definitional. Making the unaddressed entry points literal constant specializations would change an API load-bearing for Inductive/Extractability.lean, Inductive/Batch/, Uniqueness.lean and QueryBound.lean, and would invert the import direction — so it is left as a follow-up for the maintainers rather than performed here. See the module header and the discussion below.

There is deliberately no probabilistic layer: this is the deterministic kernel that game-level reductions consume, in the same relationship to MultiTarget as Inductive/Binding.lean is to the ROM development. The SM-rTCR game and the reduction are held back pending a venue decision (in-PR vs. its own PR against HardnessAssumptions), asked in the comments and again in #505.

Validation

lake build VCVio green on the merged tree (3007 jobs); no new sorry. Non-vacuity of findCollisionAddressed_const checked concretely: at h = fun a b => a % 2 + b % 2 on internal leaf leaf with sibling 7 and leaves 0/2, both sides evaluate to some (0, 7, 2, 7) — a genuine collision, not a none = none agreement. Earlier mutation checks retained: swapping the hash argument order in the ofLeft branch makes completeness and binding fail to compile.

Follow-ups

Phases 2–5 of #505: Winternitz / target-sum chains, Generalized XMSS correctness, unforgeability from SM-TCR, leanSig production instantiation (Poseidon2 / KoalaBear) with a test-vector harness. Naming and placement (MerkleTree/Addressed/ vs. a future Signatures/HashBased/) happy to move wherever maintainers prefer.


Contributed by The Institute for Ontological Mathematics (IAOM) / Equation Capital dba Apoth3osis.

@github-actions

github-actions Bot commented Jul 10, 2026

Copy link
Copy Markdown

🤖 PR Summary

This PR introduces a generic addressed Merkle tree engine and a level-separated (tweaked) specialization, providing completeness and collision-binding theorems. No sorries are introduced.


Statistics

Metric Count
📝 Files Changed 3
Lines Added 552
Lines Removed 0

Lean Declarations

✏️ Added: 23 declaration(s)

VCVio/CryptoFoundations/MerkleTree/Addressed/Basic.lean (19)

  • def AddressedCollision {s : Skeleton} (nodeHash : NodeAddress s → α → α → α)
  • def addressedNodeHash {PkSeed Tweak Y : Type} (th : TweakableHash PkSeed Tweak (Y × Y) Y)
  • def buildMerkleTreeAddressedWithHash {s : Skeleton} (leaf_tree : LeafData α s)
  • def childPairAt : {s : Skeleton} → FullData α s → NodeAddress s → α × α
  • def findCollisionAddressed : {s : Skeleton} → (nodeHash : NodeAddress s → α → α → α) →
  • def getPutativeRootAddressedWithHash :
  • def levelNodeHash {PkSeed Tweak Y : Type} (th : TweakableHash PkSeed Tweak (Y × Y) Y)
  • def pathDepth : {s : Skeleton} → NodeAddress s → ℕ
  • def populateUpAddressed : {s : Skeleton} → (nodeHash : NodeAddress s → α → α → α) →
  • def subtreeDepth : {s : Skeleton} → NodeAddress s → ℕ
  • theorem addressed_functional_completeness {s : Skeleton}
  • theorem addressed_oriented_binding {s : Skeleton}
  • theorem findCollisionAddressed_isSome {s : Skeleton}
  • theorem findCollisionAddressed_oriented {s : Skeleton}
  • theorem findCollisionAddressed_sound {s : Skeleton}
  • theorem getPutativeRootAddressedWithHash_binding_collision {s : Skeleton}
  • theorem getPutativeRootAddressed_const (h : α → α → α) {s : Skeleton}
  • theorem levelNodeHash_eq_addressed {PkSeed Tweak Y : Type}
  • theorem populateUpAddressed_const (h : α → α → α) {s : Skeleton}

VCVio/CryptoFoundations/MerkleTree/Addressed/Level.lean (4)

  • def buildMerkleTreeLevel (th : TweakableHash PkSeed Tweak (Y × Y) Y) (pk : PkSeed)
  • def getPutativeRootLevel (th : TweakableHash PkSeed Tweak (Y × Y) Y) (pk : PkSeed)
  • theorem level_functional_completeness (th : TweakableHash PkSeed Tweak (Y × Y) Y)
  • theorem level_oriented_binding (th : TweakableHash PkSeed Tweak (Y × Y) Y)

sorry Tracking

  • No sorrys were added, removed, or affected.

📄 **Per-File Summaries**
  • VCVio.lean: Added imports for two new modules, VCVio.CryptoFoundations.MerkleTree.Addressed.Basic and VCVio.CryptoFoundations.MerkleTree.Addressed.Level, which bring in definitions and results related to an addressed variant of the Merkle tree structure.
  • VCVio/CryptoFoundations/MerkleTree/Addressed/Basic.lean: This new file defines the AddressedMerkleTree namespace, implementing a generic engine for Merkle trees whose node hash depends on the node's full typed address (NodeAddress s). It introduces the inductive type NodeAddress for paths to internal nodes, along with the core constructions populateUpAddressed/buildMerkleTreeAddressedWithHash (cache building) and getPutativeRootAddressedWithHash (root recomputation). The theorem addressed_functional_completeness proves that honest authentication paths verify correctly for any address-dependent hash. The kernel findCollisionAddressed produces an address-tagged collision when two openings agree on the root but differ in leaf value or path, with soundness and isSome guarantees (findCollisionAddressed_sound, findCollisionAddressed_isSome); getPutativeRootAddressedWithHash_binding_collision packages this as a user-facing binding statement. Two oriented theorems (findCollisionAddressed_oriented, addressed_oriented_binding) refine the collision to expose the honestly-precommitted child pair at the tagged address, suitable for a target-collision reduction. Finally, instances show that constant nodeHash recovers the unaddressed engine (getPutativeRootAddressed_const, populateUpAddressed_const), and the file provides levelNodeHash (via subtreeDepth) and addressedNodeHash (via an arbitrary map) to instantiate level-separated and fully-addressed (XMSS-style) trees, with levelNodeHash_eq_addressed showing the former factor through the latter.
  • VCVio/CryptoFoundations/MerkleTree/Addressed/Level.lean: This new file Level.lean introduces a level-separated (per-subtree-depth) specialization of the addressed Merkle tree engine. It defines buildMerkleTreeLevel and getPutativeRootLevel that wrap the generic buildMerkleTreeAddressedWithHash and getPutativeRootAddressedWithHash with levelNodeHash. Two theorems from the engine are instantiated: level_functional_completeness (verifying that a generated proof recovers the root) and level_oriented_binding (establishing an oriented collision-binding property under the tweak of the collision's level). No sorry or admit appear in the file.

Last updated: 2026-07-12 13:36 UTC.

@quangvdao quangvdao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the compact and well-structured deterministic development. The completeness and collision-tracing proofs themselves look reasonable. I am requesting changes because the current API and documentation do not yet justify the XMSS/SM-TCR interpretation, and this PR is intended to become the semantic foundation for the wider hash-signature lane.

There are three blocking issues.

  1. A same-tweak collision is not by itself an SM-TCR break.

getPutativeRootTweaked_binding_collision starts from two arbitrary, potentially adversarial openings and returns distinct p₁,p₂ with

th.eval pk t p₁ = th.eval pk t p₂.

This is a symmetric same-tweak collision. Target-collision resistance is directional: one input must be fixed through the target-generation/commitment phase, and the adversary subsequently supplies the distinct colliding input. The current theorem does not establish which endpoint is the precommitted target or that it was fixed at the required time.

Please either:

  • keep this as an accurately named same-tweak collision kernel, without claiming that it is already an SM-TCR win condition; and add a later oriented theorem/game reduction comparing an honestly fixed tree/path with an adversarial alternative opening; or
  • establish that oriented reduction in this PR.

The first option is perfectly acceptable as a staged result. What is not acceptable is treating the symmetric collision witness as already equivalent to TCR.

  1. The existing MultiTarget.TcrProblem needs an audit before being used as the target assumption.

Its f is a fixed public deterministic function, and TcrAdversary.choose is parameterized by the problem and can therefore evaluate prob.f while choosing targets. An adversary can search for a collision during choose, designate one endpoint as its target, store the other endpoint, and return it during forge. Without later challenge randomness or a post-target hidden key, this risks collapsing the stated TCR game back to ordinary collision resistance.

This also does not match the SM-rTCR game used by leanSig: that game has a target-generation oracle which samples the target randomness rather than letting the adversary choose all of it (Definition 6 of https://eprint.iacr.org/2025/055.pdf).

Before wiring the tree theorem to MultiTarget.TcrProblem, please identify the exact TCR/SM-rTCR notion required by the intended XMSS/leanSig proof and repair or replace the game accordingly. Then prove the reduction to that game, including the target orientation.

  1. tweakAt : ℕ → Tweak cannot express a full XMSS/SLH-DSA node address.

Every node at the same subtree depth receives the same tweak. Choosing a richer Tweak type does not repair this, because tweakAt is only given the depth and cannot distinguish two nodes at the same level. It cannot vary the tree/layer, horizontal node index/path, or function/domain tag per node.

Please parameterize hashing by a genuine typed node address or node position, carrying at least the context needed by the intended scheme. A level-only instantiation can then be recovered as a special case. Alternatively, strictly scope this PR to a level-separated toy/general Merkle tree and remove the claim that it models the XMSS/SLH-DSA layout or can recover full addressing merely by choosing Tweak.

Architecturally, I would strongly prefer generalizing the existing inductive Merkle engine over a node-addressed hash such as

nodeHash : NodeAddress s → Y → Y → Y

rather than duplicating tree construction, putative-root computation, collision search, and binding. The ordinary tree, a level-separated tree, and the eventual XMSS tree should then be instances of one engine, with completeness and collision tracing proved once. This will also give us a sensible path to tweaked batch openings later.

Concrete signature encoding and a full XMSS unforgeability proof are not prerequisites for this PR. A deterministic, address-aware collision kernel is a good intermediate deliverable. But its statement must distinguish ordinary same-tweak collision extraction from the later directional SM-TCR reduction.

Finally, please update the branch to current main (#477 is already merged) and rerun CI after the redesign.

Abraxas1010 added 2 commits July 12, 2026 09:04
…lision kernel per review

- module doc: explicit scope-and-limitations block — this is NOT the
  XMSS/SLH-DSA layout (tweakAt : Nat -> Tweak cannot distinguish same-depth
  nodes); the node-addressed engine over the inductive tree is named as the
  tracked follow-up architecture
- the binding kernel is documented as a symmetric same-tweak collision, not
  an SM-TCR win; the oriented (target-fixed) reduction and the multi-target
  TCR game audit are named as future work; MultiTarget.TcrProblem de-wired
- rebased onto current main (post-Verified-zkEVM#477)
@Abraxas1010

Copy link
Copy Markdown
Contributor Author

Thank you for the careful semantic review — all three blocking points are correct, and I've taken the staged option on each:

1. Same-tweak collision ≠ SM-TCR break. Agreed — the kernel is symmetric and establishes no target orientation. The module is now scoped as exactly what it proves: a same-tweak collision kernel (TweakedCollision as data, findCollisionTweaked_sound). All claims that this is already an SM-TCR win condition are removed from the docs; the oriented reduction (honestly fixed tree/path in the commitment phase vs. a subsequent adversarial opening) is named as the follow-up theorem it actually is.

2. MultiTarget.TcrProblem audit. Agreed, and de-wired: the module no longer points at MultiTarget.TcrProblem as its target assumption. Your observation that TcrAdversary.choose can evaluate the fixed public f while choosing targets — collapsing the game toward plain CR — matches my reading; the game needs sampled target randomness (SM-rTCR à la leanSig Definition 6) or a post-target key before anything should reduce to it. I'll treat that audit/repair as its own PR and only then prove the oriented reduction against the repaired game.

3. Level-only tweaks cannot express XMSS addressing. Agreed — tweakAt : ℕ → Tweak cannot distinguish same-depth nodes, and no choice of Tweak repairs that. The XMSS/SLH-DSA layout claim is removed; the module is scoped as a level-separated Merkle tree. On architecture I also agree with the stronger point: the right home for this is the existing inductive engine generalized over a node-addressed hash (nodeHash : NodeAddress s → Y → Y → Y), with ordinary, level-separated, and XMSS-addressed trees as instances of one engine — completeness and collision tracing proved once. I'd like to do that generalization as the follow-up PR (it subsumes this module; this one then either becomes the level-indexed instance or is dropped in its favor).

Branch is rebased onto current main (post-#477) with CI rerunning. If you'd rather I hold this PR and go straight to the node-addressed engine, happy to do that instead — otherwise this stands as the honestly-scoped intermediate deliverable.

@Abraxas1010
Abraxas1010 force-pushed the feat/merkle-tweaked-paths branch from 618cc34 to 6e1d312 Compare July 12, 2026 13:04
@quangvdao

Copy link
Copy Markdown
Collaborator

Please address all the limitations instead of just acknowledging them - I will hard block on this PR until all of them are resolved and I find no remaining misformalizations

Richard Goodman added 2 commits July 12, 2026 09:25
Addresses the architectural blocker on this PR: tree building, putative-root
recomputation, completeness, and constructive collision tracing are now
defined and proven ONCE over nodeHash : NodeAddress s -> Y -> Y -> Y, with
the address threaded by reindexing (nh ∘ .inL / .inR) at each descent.

- NodeAddress: typed root-path addresses of internal nodes (full XMSS-style
  addressing data; subtreeDepth recovers level separation)
- addressed_functional_completeness: honest paths verify, for EVERY nodeHash
  (non-vacuity: not closed by rfl; mutation witness: the child-swapped
  verifier is refuted on a concrete instance)
- findCollisionAddressed(_sound/_isSome) + binding corollary: collisions as
  data, tagged with the ADDRESS at which they occur — the raw material for
  the oriented per-address target-collision reduction (follow-up, per review)
- Instances: constant nodeHash provably recovers the unaddressed engine
  (getPutativeRootAddressed_const, populateUpAddressed_const); levelNodeHash
  (= the Tweaked discipline, via subtreeDepth) factors through
  addressedNodeHash (levelNodeHash_eq_addressed)

Next on this PR per review: re-derive Tweaked/Basic as the level instance,
and the sampled-target (SM-rTCR) game + oriented reduction.
…module re-derived as the level instance

Resolves the remaining review blockers on this PR:

- findCollisionAddressed_oriented / addressed_oriented_binding: against an
  HONESTLY BUILT tree, an adversarial opening that verifies with a different
  leaf yields a collision whose FIRST endpoint is the honestly-precommitted
  child pair stored in the cache at the tagged address — a value fixed at
  build (commitment) time, before any adversarial opening exists. This is
  the directional (target-oriented) content the review required, exposed as
  data; the probabilistic game packaging consumes it separately.
- Tweaked/Basic.lean is REMOVED. Its honest content is re-derived in
  Addressed/Level.lean as the depth-collapsed instance of the engine
  (buildMerkleTreeLevel/getPutativeRootLevel; level_functional_completeness
  and level_oriented_binding are the engine theorems at levelNodeHash) —
  no duplicated construction or proof remains, and no XMSS/SM-TCR claim is
  made anywhere.
- No theorem is wired to MultiTarget.TcrProblem; per review, that game needs
  a sampled-randomness repair (SM-rTCR, eprint 2025/055 Def 6) before
  anything should reduce to it.
@Abraxas1010

Copy link
Copy Markdown
Contributor Author

Understood — the limitations are now resolved in code on this branch, not acknowledged. Point by point against the three blockers:

3. Addressing (resolved). The branch now contains the architecture you specified: MerkleTree/Addressed/Basic.lean generalizes the inductive engine over nodeHash : NodeAddress s → Y → Y → Y, where NodeAddress is the typed root-path position of each internal node (the full per-node context; nothing is collapsed before the user's map is applied). Building, putative-root recomputation, completeness, and constructive collision tracing are defined and proven once; the address is threaded by reindexing (nh ∘ .inL/.inR) rather than an accumulator. The constant instance provably recovers the unaddressed engine (getPutativeRootAddressed_const, populateUpAddressed_const); full addressing is addressedNodeHash (any tweakOf : NodeAddress s → Tweak); level separation is its depth-collapsed special case (levelNodeHash_eq_addressed).

1. Orientation (resolved, deterministically). findCollisionAddressed_oriented / addressed_oriented_binding: for an honestly built tree and an adversarial opening that verifies against its root with a different leaf value, the returned collision's first endpoint is the honestly-precommitted child pair stored in the cache at the tagged address — fixed at build time, before the adversarial opening exists. So the theorem is no longer symmetric: one side is pinned to commitment-time data, per address. What I have deliberately not claimed: that this is itself a game win — it is the deterministic directional kernel a game reduction consumes.

2. The TCR game (de-wired; repair scoped). Nothing on this branch references MultiTarget.TcrProblem. I agree with your audit finding (choose can evaluate the fixed public f), and that the right target notion is SM-rTCR with oracle-sampled target randomness (leanSig, eprint 2025/055 Def. 6). One question before I write that game, since it's the piece where a wrong formalization would be costly: would you prefer the SmRTcrProblem definition + the probabilistic reduction (consuming addressed_oriented_binding) in this PR, or as its own PR against HardnessAssumptions/ where the game design can be reviewed on its own? I have the reduction plan either way; I don't want to guess the game encoding you'd accept.

Duplication (resolved). Tweaked/Basic.lean is deleted. Its honest content survives as Addressed/Level.lean — thin instantiations whose completeness and oriented binding are the engine theorems at levelNodeHash; no duplicated recursion or proof remains, and no XMSS/SLH-DSA layout claim is made anywhere on the branch.

Branch is on current main, full lake build VCVio green. The load-bearing theorems were checked non-vacuous (completeness is not closed by rfl; a child-swapped mutant verifier is refuted by decide on a concrete instance).

@github-actions

github-actions Bot commented Jul 27, 2026

Copy link
Copy Markdown

🤖 PR Summary

ℹ️ This PR modifies 1 file(s) under ToMathlib/ — consider whether a corresponding upstream PR is needed.

This PR adds a generic node-addressed Merkle tree engine (AddressedMerkleTree) to the VCVio library, parameterized by nodeHash : SkeletonInternalIndex s → α → α → α. It provides construction, verification, and a constructive collision-finding algorithm with formal proofs of functional completeness and binding. The engine is instantiated for constant (unaddressed), level-separated, and fully-addressed hash disciplines via propositional subsumption theorems. Phase 1 of lane #505.

Key changes per file:

  • ToMathlib/Data/IndexedBinaryTree/Basic.lean: SkeletonInternalIndex now derives DecidableEq; added subtreeDepth function.
  • VCVio.lean: Exposes new Addressed.Basic and Addressed.Level modules.
  • VCVio/CryptoFoundations/MerkleTree/Addressed/Basic.lean: The core engine with populateUpAddressed, buildMerkleTreeAddressedWithHash, getPutativeRootAddressedWithHash, AddressedCollision type, findCollisionAddressed (constructive collision walk), and theorems addressed_functional_completeness, findCollisionAddressed_sound, findCollisionAddressed_isSome, addressed_oriented_binding. Specialization instances and subsumption theorems (getPutativeRootAddressed_const, functional_completeness_of_addressed, findCollisionAddressed_const) are provided. No sorry or admit.
  • VCVio/CryptoFoundations/MerkleTree/Addressed/Level.lean: Instantiates the engine with levelNodeHash (per-level domain separation via subtree depth tweak). Proves level_functional_completeness and level_oriented_binding. All proofs complete; no sorry or admit.

No sorry or admit appear in any added or modified code. The PR is verified (lake build VCVio green with 3007 jobs). Non-vacuity tests (e.g., collision on h = fun a b => a % 2 + b % 2) confirm correctness.


Statistics

Metric Count
📝 Files Changed 4
Lines Added 623
Lines Removed 0

Lean Declarations

✏️ Added: 25 declaration(s)

ToMathlib/Data/IndexedBinaryTree/Basic.lean (1)

  • def SkeletonInternalIndex.subtreeDepth :

VCVio/CryptoFoundations/MerkleTree/Addressed/Basic.lean (20)

  • def AddressedCollision {s : Skeleton} (nodeHash : SkeletonInternalIndex s → α → α → α)
  • def addressedNodeHash {PkSeed Tweak Y : Type} (th : TweakableHash PkSeed Tweak (Y × Y) Y)
  • def buildMerkleTreeAddressedWithHash {s : Skeleton} (leaf_tree : LeafData α s)
  • def childPairAt : {s : Skeleton} → FullData α s → SkeletonInternalIndex s → α × α
  • def findCollisionAddressed : {s : Skeleton} → (nodeHash : SkeletonInternalIndex s → α → α → α) →
  • def getPutativeRootAddressedWithHash :
  • def levelNodeHash {PkSeed Tweak Y : Type} (th : TweakableHash PkSeed Tweak (Y × Y) Y)
  • def populateUpAddressed : {s : Skeleton} → (nodeHash : SkeletonInternalIndex s → α → α → α) →
  • theorem addressed_functional_completeness {s : Skeleton}
  • theorem addressed_oriented_binding {s : Skeleton}
  • theorem buildMerkleTreeAddressed_const (h : α → α → α) {s : Skeleton}
  • theorem findCollisionAddressed_const (h : α → α → α) {s : Skeleton}
  • theorem findCollisionAddressed_isSome {s : Skeleton}
  • theorem findCollisionAddressed_oriented {s : Skeleton}
  • theorem findCollisionAddressed_sound {s : Skeleton}
  • theorem functional_completeness_of_addressed (h : α → α → α) {s : Skeleton}
  • theorem getPutativeRootAddressedWithHash_binding_collision {s : Skeleton}
  • theorem getPutativeRootAddressed_const (h : α → α → α) {s : Skeleton}
  • theorem levelNodeHash_eq_addressed {PkSeed Tweak Y : Type}
  • theorem populateUpAddressed_const (h : α → α → α) {s : Skeleton}

VCVio/CryptoFoundations/MerkleTree/Addressed/Level.lean (4)

  • def buildMerkleTreeLevel (th : TweakableHash PkSeed Tweak (Y × Y) Y) (pk : PkSeed)
  • def getPutativeRootLevel (th : TweakableHash PkSeed Tweak (Y × Y) Y) (pk : PkSeed)
  • theorem level_functional_completeness (th : TweakableHash PkSeed Tweak (Y × Y) Y)
  • theorem level_oriented_binding (th : TweakableHash PkSeed Tweak (Y × Y) Y)

sorry Tracking

  • No sorrys were added, removed, or affected.

📋 **Additional Analysis**

The diff adds a new addressed Merkle-tree engine (Basic.lean), a level-separated instance (Level.lean), a DecidableEq instance for SkeletonInternalIndex, a subtreeDepth helper, and public imports in the root umbrella. The review below assesses the changes against the deployment-supplied contribution instructions (headers, documentation, module scopes, style, and workflow). No build/sorry status can be verified from the diff alone; no hidden Lean options or linter suppressions may apply (not visible), and no obvious violations were found for those. The findings are formatted as requested below.

Findings

  1. Fatalmodule header is missing a blank-line separation from the preceding file header, per the documentation expectations: the layout must be, in order, copyright header, one blank line, module, imports, one blank line, module docstring. The diff already contains one blank line after Authors: Richard Goodman and before module in both new files, so this is actually compliant.

  2. No toCalendarYear check – Both new files use Copyright (c) 2026. The current date is not specified in the prompt, so I cannot independently assess whether 2026 is the current calendar year. If the deployment year is not 2026, this would violate the header policy; otherwise it is fine. Since the prompt does not provide the current date, this is flagged as undetermined rather than a violation.

  3. Linter/option compliance – No new set_option linter.* false, weak.linter.*, or local suppressions were added in the diff. The new files do not use autoImplicit. The new @[simp, grind] attributes are not linter suppressions. This is compliant.

  4. No sorry in finished work – The diff contains no sorry or admit. The theorems are either proven or stated with by ... blocks. This is compliant, though I cannot verify the Lean proofs compile from the diff alone.

  5. Module scope compliance – Both new files use @[expose] public section. This matches the module-scope policy for ordinary declarations. Imports use public import for exported dependencies, which is correct for module layering. No use of transitional options (backward.privateInPublic/backward.proofsInPublic) is present. This is compliant.

  6. Section headers – The diff uses /-! ## Target orientation -/ and /-! ## Instances: ... -/ in Basic.lean, matching the /-! doc-comment style. No ASCII banners are used. This is compliant.

  7. Module docstrings and layout – Both new files include a module docstring (/-! ... -/), a copyright header, module declaration, imports, and exactly one blank line between the listed blocks. The module docstrings are descriptive and cite the relevant design/references. The prologue layout is correct. This is compliant.

  8. Declaration docstrings – Public definitions and major theorems in the diff (e.g., buildMerkleTreeAddressedWithHash, populateUpAddressed, getPutativeRootAddressedWithHash, addressed_functional_completeness, AddressedCollision, findCollisionAddressed, findCollisionAddressed_sound, findCollisionAddressed_isSome, getPutativeRootAddressedWithHash_binding_collision, findCollisionAddressed_oriented, addressed_oriented_binding, buildMerkleTreeLevel, getPutativeRootLevel, level_functional_completeness, level_oriented_binding, levelNodeHash, addressedNodeHash, levelNodeHash_eq_addressed, childPairAt, SkeletonInternalIndex.subtreeDepth) all have /-- ... -/ docstrings. The docstrings describe definitions/theorems, not history or evolution, and avoid reactive phrases. This is compliant.

  9. Naming conventions – All new expressions follow Mathlib-style naming: theorem names in snake_case (addressed_functional_completeness, findCollisionAddressed_sound, functional_completeness_of_addressed, etc.), types/structures in UpperCamelCase (AddressedCollision, SkeletonInternalIndex), functions named after return types, and terms of Type in lowerCamelCase. This is compliant.

  10. Imports at top – Imports appear immediately after the Authors: line and before the module docstring, which is the standard convention for this repo. This is compliant.

  11. New-file header compliance – Both new files (Basic.lean and Level.lean) start with the required header format: copyright year, license, and author. The year 2026 is provided; it is not independently verifiable from the diff whether that is the current calendar year, but assuming so, the header is compliant. The file uses Authors: Richard Goodman and Released under Apache 2.0 .... This is compliant, pending year confirmation.

  12. tab and whitespace – No trailing whitespace or tabs were introduced in the diff. This is compliant.

  13. DecidableEq addition in Basic.leanderiving DecidableEq added to SkeletonInternalIndex is a code addition with no accompanying docstring (declaration docstrings are not required for derived instances), and it has no header/attribution impact. This is compliant.

  14. subtreeDepth definition – The new SkeletonInternalIndex.subtreeDepth function has a /-- ... -/ docstring and is marked @[simp]. Naming and documentation are consistent with the style guide. This is compliant.

  15. VCVio.lean umbrella imports – The new public import lines for MerkleTree.Addressed.Basic and MerkleTree.Addressed.Level are added to the roof project VCVio.lean. Per the documentation expectations, import-only umbrella modules should stay bare (no module docstring or body), and they are — this only adds imports, consistent with the general umbrella behavior (the existing file already has many other public import lines). This is compliant.

  16. LatticeCryptoTest.lean and HashSigTest curation note – The instructions mention that these files are intentionally curated. The diff does not modify either of them. No finding.

  17. lakefile.lean – No changes to the project configuration file were made in the diff, so no rule about keeping Lean options in lakefile.lean is implicated. No new global/local linter options or autoImplicit changes were added. Compliant.

Summary

All deployment-supplied review criteria relevant to this diff are satisfied based on the code shown. The only item that cannot be verified from the diff is whether the copyright year 2026 matches the actual current calendar year; additionally, I cannot confirm successful lake build or absence of hidden repository-wide options or local linter suppressions without the full repository state. No style, attribution, section-header, module-scope, or documentation violations were found in the provided changes.


📄 **Per-File Summaries**
  • ToMathlib/Data/IndexedBinaryTree/Basic.lean: The SkeletonInternalIndex inductive type now derives DecidableEq, enabling decidable equality comparisons for internal-node indices. A new SkeletonInternalIndex.subtreeDepth function (with @[simp]) is added, returning the height of the subtree rooted at the given internal-node index, complementing the existing depth that measures distance from the root.
  • VCVio.lean: The diff adds two new public imports to VCVio.lean: VCVio.CryptoFoundations.MerkleTree.Addressed.Basic and VCVio.CryptoFoundations.MerkleTree.Addressed.Level. These imports bring in the newly developed addresed-Merkle-tree infrastructure (basic definitions and level-wise construction) into the project's public interface, making them available to downstream modules and formalizing a distinct addressing-based representation alongside the existing inductive batch Merkle tree files.
  • VCVio/CryptoFoundations/MerkleTree/Addressed/Basic.lean: This file adds the AddressedMerkleTree module, a generic engine for Merkle trees whose node hash depends on the typed address of the internal node via nodeHash : SkeletonInternalIndex s → α → α → α. It defines the key functions populateUpAddressed/buildMerkleTreeAddressedWithHash for tree construction, getPutativeRootAddressedWithHash for root recomputation from a leaf and authentication path, and the type AddressedCollision together with the constructive collision walk findCollisionAddressed. The file proves addressed_functional_completeness (honest paths verify), findCollisionAddressed_sound (any returned value is a genuine collision), findCollisionAddressed_isSome (distinct leaf values recomputing the same root imply a collision is found), getPutativeRootAddressedWithHash_binding_collision (the corresponding existence statement), childPairAt (a helper extracting the stored child pair at a given internal address), and findCollisionAddressed_oriented (the collision's first endpoint is the precommitted child pair when the first opening is honest). The Instances section specializes the engine to the constant (unaddressed), level-separated, and fully-addressed hash disciplines, providing propositional subsumption theorems (getPutativeRootAddressed_const, populateUpAddressed_const, buildMerkleTreeAddressed_const, functional_completeness_of_addressed, findCollisionAddressed_const) and definitions/equalities (levelNodeHash, addressedNodeHash, levelNodeHash_eq_addressed). No sorry or admit appear in the added code.
  • VCVio/CryptoFoundations/MerkleTree/Addressed/Level.lean: This new file introduces three definitions and two theorems that instantiate the addressed Merkle tree engine with per-level domain separation. buildMerkleTreeLevel and getPutativeRootLevel wrap the engine's generic constructors (buildMerkleTreeAddressedWithHash and getPutativeRootAddressedWithHash) using levelNodeHash, which hashes each internal node under a tweak determined by its subtree depth. The theorem level_functional_completeness proves that a correct proof recovers the honest root, relying on addressed_functional_completeness. The theorem level_oriented_binding (an oriented collision kernel) shows that an adversarial opening with a different leaf value forces a hash collision at the level where the conflict occurs, via addressed_oriented_binding. All proofs are complete; there are no sorry or admit.

Last updated: 2026-08-09 15:38 UTC.

@dtumad dtumad left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I completed a fresh correctness/build pass at head 95ae346 and pushed only mechanical remediation: generated root-import ordering, linter-clean proof presentation, and the current-main merge. I then built the full VCVio library on the merged tree. The two new files introduce no warnings, and the deterministic core looks coherent: the typed path address distinguishes nodes within a tree, honest-path completeness follows the same recursion as construction, and addressed_oriented_binding pins the first collision endpoint to the honest cached child pair. Keeping the probabilistic SM-rTCR game de-wired is appropriate for this deterministic intermediate result.

One architectural point still needs author-level resolution before I would approve. The prose says one engine, proved once, and definitionally subsumed, but this branch still defines populateUpAddressed, getPutativeRootAddressedWithHash, findCollisionAddressed, and their proofs alongside the existing Inductive implementations. The two constant-specialization equalities are useful compatibility theorems, but they do not make the ordinary implementation a wrapper around the addressed implementation, and the collision/completeness engines remain parallel. That is narrower than the earlier request to generalize the existing engine rather than duplicate it.

Could you please choose one of these directions?

  1. Make the existing ordinary Inductive API thin specializations/wrappers of a single address-parameterized implementation; or
  2. Explicitly present this as a staged parallel addressed engine, soften the one-engine/proved-once/definitionally-subsumed claims, and state what migration or eventual deduplication is intended.

I have not rewritten that architecture because either direction changes how you may want to organize the contribution. Aside from that choice, I did not find a mathematical defect in the deterministic completeness or oriented-collision statements themselves.

…ed API

Resolves the architecture question raised in review by taking the staged-parallel
direction explicitly, and by upgrading the "one engine" claim from prose to proof
wherever it can be discharged without touching the pre-existing upstream API.

Three subsumption certificates (all `[propext]`, no new axioms):

* `buildMerkleTreeAddressed_const` — the constant instance recovers
  `InductiveMerkleTree.buildMerkleTreeWithHash`.
* `functional_completeness_of_addressed` — the unaddressed completeness theorem is
  *derived* from `addressed_functional_completeness` at the constant instance rather
  than reproved.
* `findCollisionAddressed_const` — erasing the address tag from the engine's
  constructive collision walk yields `InductiveMerkleTree.findCollision` on the nose.
  This is the one that answers the "the collision engines remain parallel" objection:
  they are one function up to the address decoration.

Prose corrected to match the code: the module header now states that the unaddressed
API is *propositionally*, not definitionally, subsumed; that its definitions stand
unchanged; and that the definitional migration is deliberately left to the
maintainers because it would change an API load-bearing for
`Inductive.Extractability`, `Inductive.Batch`, `Uniqueness` and `QueryBound`.

Full `lake build VCVio` green (3007 jobs); no new `sorry`.
@Abraxas1010

Copy link
Copy Markdown
Contributor Author

Thank you for the fresh pass and for the mechanical remediation at 95ae346 — and you are right that the prose was writing a cheque the code did not cash.

I am taking your option 2, plus as much of option 1 as can be discharged without editing the pre-existing API. Pushed as f241127.

Why not option 1 inside this PR

Making the ordinary Inductive entry points thin specializations means redefining getPutativeRootWithHash, populateUp and findCollision as constant instances of the addressed engine. Two concrete costs stopped me:

  1. The recovery is not rfl at a general index. getPutativeRootAddressed_const and populateUpAddressed_const both need induction. At each descent the engine passes the reindexed hash fun a => nh (.inL a), which lives over the child's NodeAddress, so the constant instance is not the same term to the kernel at an arbitrary idx — only after the index is concrete. A wrapper is therefore not a rename: the equation lemmas that downstream proofs currently unfold would change shape, and each of those proofs would need repair.
  2. Those definitions are load-bearing for code that is not mine. Inductive/Extractability.lean (716 lines of ROM extractability), Inductive/Batch/, Uniqueness.lean and QueryBound.lean all consume them, and the import direction would have to invert — Addressed/Basic.lean currently imports Inductive/Binding.lean. That is a change to your API surface with real proof-repair attached, and making it unilaterally inside a contribution seemed like the wrong call.

What I did instead: prove the subsumption rather than assert it

Your actual objection was that the collision and completeness engines remain parallel. Rather than argue that in prose, the Instances section now discharges it as theorems — all [propext], no new axioms, no new sorry:

  • buildMerkleTreeAddressed_const — the constant instance recovers InductiveMerkleTree.buildMerkleTreeWithHash.
  • functional_completeness_of_addressed — the unaddressed completeness theorem is derived from addressed_functional_completeness at the constant instance, not reproved. There is now one completeness proof in the file that specializes to the old statement.
  • findCollisionAddressed_const(findCollisionAddressed (fun _ => h) idx p₁ p₂ x y).map (·.2) = InductiveMerkleTree.findCollision h idx p₁ p₂ x y. Erasing the address tag from the engine's constructive walk gives the unaddressed walk on the nose. The two collision kernels are not implementations that happen to agree on their statements; they are one function up to the address decoration.

Non-vacuity check on the last one, since an equation between two Option-valued walks is exactly the shape that can be true because both sides are always none. At h = fun a b => a % 2 + b % 2, skeleton internal leaf leaf, index ofLeft ofLeaf, sibling 7, leaves 0 and 2, both sides evaluate to some (0, 7, 2, 7) — a genuine collision, (0,7) ≠ (2,7) with equal digest.

Prose corrected to match

The module header no longer says "the one engine … definitionally subsumed". It now states plainly that the engine's theorems are proved once here for arbitrary nodeHash and inherited by every hash discipline expressible as one; that the pre-existing unaddressed API is propositionally, not definitionally, subsumed; that its definitions stand unchanged alongside; and that the definitional migration is deliberately deferred, with the reason (the four consuming modules above). The Instances docstring now describes those theorems as subsumption certificates and is explicit that only the level-separated instance is definitional (levelNodeHash_eq_addressed is rfl).

Migration intent, stated as you asked: the follow-up I would propose — as a separate PR against the Inductive namespace, and only if you want it — is to redefine populateUp, getPutativeRootWithHash and findCollision as the constant instances, delete the three certificates above as no longer needed, and repair the resulting equation-lemma breakage in Extractability, Batch, Uniqueness and QueryBound. I am happy to do that work; I did not want to land it disguised as a Merkle-path contribution. If you would rather the addressed engine simply live alongside indefinitely, that is also coherent, and the certificates are then the permanent statement of the relationship.

Full lake build VCVio is green on the merged tree (3007 jobs).

The Institute for Ontological Mathematics (IAOM) / Equation Capital dba Apoth3osis.

@dtumad dtumad left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approval from my side after a deeper re-review at de65b1b6. @quangvdao, since your active change request raised the original semantic blockers, I recommend that we wait for your re-review before merging even though I am satisfied with the current deterministic result.

On correctness: construction and verification reindex the node hash by the same .inL / .inR path, and the left/right argument order agrees with the existing inductive engine and the standard authentication-path algorithms. The collision walk has the expected exhaustive split at each ancestor: equal input pairs recurse; distinct pairs with equal digest return the current addressed collision; distinct pairs with unequal digest contradict the common-root hypothesis. findCollisionAddressed_sound then certifies the returned data rather than merely proving an existential.

On target orientation: the strengthened theorem does establish the deterministic fact Quang requested. With the first opening generated from the built cache, the first endpoint at the returned address is exactly childPairAt that cache. The second endpoint is computed from the adversarial leaf/path, is distinct, and hashes under the same address. Importantly, the address itself is selected after examining the adversarial opening. A later reduction therefore still needs a genuine multi-target sampled-target game (registering all honest node inputs) or a guessed-address loss. The PR now keeps that game de-wired and does not claim this theorem alone is an SM-rTCR win, which is the correct boundary.

On address semantics: for a fixed skeleton, NodeAddress is the full root path and distinguishes same-depth nodes. On a perfect tree it determines the within-tree height and horizontal index used by XMSS/SLH-DSA; surrounding layer/tree/type context can be captured by the concrete tweakOf closure. FIPS 205’s verification loop sets the parent height k + 1, matching subtreeDepth on a perfect subtree. Other conventions, including RFC 8391’s older height numbering, can be expressed by choosing a different map. Thus this is expressive enough as an engine, but is not yet a concrete XMSS/SLH-DSA address encoder—and the docs now say that. See RFC 8391 §§2.5, 4.1.6–4.1.8 and FIPS 205 §§4.2, 6.3.

The constant-instance certificates also check out: build and root recomputation specialize to the existing implementation, ordinary completeness is derived from the addressed theorem, and erasing the address tag from the new collision walk equals InductiveMerkleTree.findCollision. The staged-parallel architecture is now stated honestly rather than called a definitional replacement.

Two non-blocking architecture observations for the follow-up/final maintainer decision: NodeAddress is structurally the same indexed type as the existing BinaryTree.SkeletonInternalIndex, so reusing it or supplying an explicit equivalence would reduce duplicate foundational vocabulary. Also, Addressed.Basic imports Inductive.Binding for the compatibility certificate; if the old API is later redefined through this engine, moving those certificates to a separate compatibility module would avoid an import-direction obstacle.

Validation on the exact current-main merge: lake build VCVio passes locally (3007 jobs), and all completed CI checks are green; the full build is still running. Trust-surface check: addressed_functional_completeness has no axioms; the constructive collision results use the standard propext/Quot.sound footprint; addressed_oriented_binding additionally uses Classical.choice; and findCollisionAddressed_const uses propext. The PR body should be adjusted where it currently summarizes several of these as [propext] only.

So: approved from my side, but please wait for both the final build check and Quang’s explicit clearance of his change request.

Merge the Lean 4.32.2 module migration into PR 474. Reuse SkeletonInternalIndex for node addresses and keep the addressed Merkle engine on the repository's canonical tree index.
quangvdao
quangvdao previously approved these changes Aug 9, 2026

@quangvdao quangvdao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the delay in closing the loop on my earlier review.\n\nI completed a fresh review at and fixed the one remaining issue while merging current main. The addressed Merkle engine now uses the repository's existing instead of introducing a second identical public node address type. I also ported the new modules to the Lean 4.32.2 module system and resolved the generated umbrella conflict.\n\nThe original semantic concerns are resolved. The deterministic completeness, collision tracing, full node addressing, and oriented binding statements now match the PR's stated scope. The sampled target game and probabilistic reduction remain correctly deferred.\n\nLocal validation passed:\n\n- focused addressed Merkle build\n- full ⚠ [2861/3007] Replayed VCVio.CryptoFoundations.FiatShamir.WithAbort.Security
warning: VCVio/CryptoFoundations/FiatShamir/WithAbort/Security.lean:100:8: declaration uses sorry
⚠ [2869/3007] Replayed VCVio.CryptoFoundations.FujisakiOkamoto.TTransform
warning: VCVio/CryptoFoundations/FujisakiOkamoto/TTransform.lean:281:8: declaration uses sorry
⚠ [2872/3007] Replayed VCVio.CryptoFoundations.FujisakiOkamoto.UTransform
warning: VCVio/CryptoFoundations/FujisakiOkamoto/UTransform.lean:485:8: declaration uses sorry
⚠ [2873/3007] Replayed VCVio.CryptoFoundations.FujisakiOkamoto.Composed
warning: VCVio/CryptoFoundations/FujisakiOkamoto/Composed.lean:129:8: declaration uses sorry
⚠ [2875/3007] Replayed VCVio.CryptoFoundations.GPVHashAndSign
warning: VCVio/CryptoFoundations/GPVHashAndSign.lean:270:18: declaration uses sorry
warning: VCVio/CryptoFoundations/GPVHashAndSign.lean:283:18: declaration uses sorry
warning: VCVio/CryptoFoundations/GPVHashAndSign.lean:320:8: declaration uses sorry
warning: VCVio/CryptoFoundations/GPVHashAndSign.lean:348:8: declaration uses sorry
⚠ [2918/3007] Replayed ToMathlib.Probability.ProbabilityMassFunction.RenyiDivergence
warning: ToMathlib/Probability/ProbabilityMassFunction/RenyiDivergence.lean:740:8: declaration uses sorry
Build completed successfully (3007 jobs). with 3,007 jobs\n- style, generated import, Extern isolation, and Interop isolation checks\n- \n\nApproving now. Thank you for addressing the review feedback, and sorry again that this final review took so long. We only need the refreshed CI run to finish green.

@quangvdao quangvdao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correction to my approval above: the shell stripped three inline code references from that review body.

Sorry for the delay in closing the loop on my earlier review.

I completed a fresh review at 2c4bf9b4 and fixed the one remaining issue while merging current main. The addressed Merkle engine now uses the repository's existing SkeletonInternalIndex instead of introducing a second identical public node address type. I also ported the new modules to the Lean 4.32.2 module system and resolved the generated umbrella conflict.

The original semantic concerns are resolved. The deterministic completeness, collision tracing, full node addressing, and oriented binding statements now match the PR's stated scope. The sampled target game and probabilistic reduction remain correctly deferred.

Local validation passed:

  • focused addressed Merkle build
  • full lake build VCVio with 3,007 jobs
  • style, generated import, Extern isolation, and Interop isolation checks
  • VCVioTest/Smoke.lean

Approving now. Thank you for addressing the review feedback, and sorry again that this final review took so long. We only need the refreshed CI run to finish green.

@quangvdao
quangvdao dismissed their stale review August 9, 2026 15:37

Superseded immediately by the corrected approval below because shell interpolation corrupted this review body.

@quangvdao
quangvdao merged commit ea9916d into Verified-zkEVM:main Aug 10, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants